import { PureCallback } from '../Function'
import { StrUtil } from '../util/StrUtil'

// noinspection JSUnusedGlobalSymbols
/**
 * 时间单位 <br>
 * 提供更可读的计时操作 <br>
 * 例:
 * <pre>
 *     // 3 秒后执行
 *     const timer = TimeUnit.SECOND.setTimeout(() => {
 *         // ......
 *     }, 3)
 *
 *     // 设置过期时间: 一周
 *     const expirationTime = TimeUnit.WEEK.toMillis()
 * </pre>
 * <p>
 *     {@link #toMillis()} 提供和其他基于毫秒的计时操作最大的互操作性
 * </p>
 *
 * @author 琉璃
 * @date 2022年1月25日 14点36分
 */
export class TimeUnit {
    /* 毫秒 */
    private static readonly MILLISECOND_VALUE: number = 1
    /* 秒 */
    private static readonly SECOND_VALUE: number = TimeUnit.MILLISECOND_VALUE * 1000
    /* 分钟 */
    private static readonly MINUTE_VALUE: number = TimeUnit.SECOND_VALUE * 60
    /* 小时 */
    private static readonly HOUR_VALUE: number = TimeUnit.MINUTE_VALUE * 60
    /* 天 */
    private static readonly DAY_VALUE: number = TimeUnit.HOUR_VALUE * 24
    /* 周 */
    private static readonly WEEK_VALUE: number = TimeUnit.DAY_VALUE * 7
    /* 月 */
    private static readonly MONTH_VALUE: number = TimeUnit.DAY_VALUE * 30

    /* 静态实例: 毫秒 */
    public static readonly MILLISECOND: TimeUnit = new TimeUnit(TimeUnit.MILLISECOND_VALUE)
    /* 静态实例: 秒 */
    public static readonly SECOND: TimeUnit = new TimeUnit(TimeUnit.SECOND_VALUE)
    /* 静态实例: 分钟 */
    public static readonly MINUTE: TimeUnit = new TimeUnit(TimeUnit.MINUTE_VALUE)
    /* 静态实例: 小时 */
    public static readonly HOUR: TimeUnit = new TimeUnit(TimeUnit.HOUR_VALUE)
    /* 静态实例: 天 */
    public static readonly DAY: TimeUnit = new TimeUnit(TimeUnit.DAY_VALUE)
    /* 静态实例: 周 7天 */
    public static readonly WEEK: TimeUnit = new TimeUnit(TimeUnit.WEEK_VALUE)
    /* 静态实例: 旬月 10天 */
    public static readonly 旬月: TimeUnit = new TimeUnit(TimeUnit.DAY_VALUE * 10)
    /* 静态实例: 月 30天 */
    public static readonly MONTH: TimeUnit = new TimeUnit(TimeUnit.MONTH_VALUE)

    /**
     * 当前时间单位的刻度毫秒值
     * @type {number}
     * @private
     */
    private readonly value: number

    /**
     * 构造, 除非需要自定义时间单位, 否则使用静态实例常量即可
     * @param {number} value
     */
    constructor(value: number) {
        this.value = value
    }

    /**
     * 转换到毫秒
     * @param {number} duration 数量, 默认 1
     * @returns {number} 毫秒值; 除非 size 有问题 否则永为整数
     */
    public toMillis(duration: number = 1): number {
        return this.value * duration
    }

    /**
     * 转换到秒
     * 如果当前单位小于秒, 转换会出现小数
     * @param {number} duration
     */
    public toSecond(duration: number): number {
        return duration * ( this.value / 1000 )
    }

    /**
     * 转换到分钟
     * 如果当前单位小于分钟, 转换会出现小数
     * @param {number} duration
     */
    public toMinutes(duration: number) {
        return duration * ( this.value / 60000 )
    }

    /**
     * 转换到小时
     * 如果当前单位小于小时, 转换会出现小数
     * @param {number} duration
     */
    public toHours(duration: number) {
        return duration * ( this.value / 3600000 )
    }

    /**
     * 转换到天
     * 如果当前单位小于天, 转换会出现小数
     * @param {number} duration
     */
    public toDays(duration: number) {
        return duration * ( this.value / 86400000 )
    }

    /* 实用工具 */

    /**
     * 延迟执行
     * @param {PureCallback} fun
     * @param {number} timeout
     * @returns {number}
     */
    public setTimeout(fun: PureCallback, timeout: number): number {
        return setTimeout(fun, this.value * timeout)
    }

    /**
     * 定时器
     * @param {PureCallback} fun
     * @param {number} timeout
     * @returns {number}
     */
    public setInt(fun: PureCallback, timeout: number): number {
        return setInterval(fun, this.value * timeout)
    }

    /**
     * 显示时间, 不考虑溢出
     * @param {number} time 当前单位的毫秒值
     * @param {boolean} h 主动显示, 即使为 00
     * @param {boolean} m 主动显示, 即使为 00
     * @param {boolean} s 主动显示, 即使为 00
     * @param {boolean} ms 是否显示
     * @return {string} 时间字符串
     */
    public display(time: number, h: boolean = false, m: boolean = true, s: boolean = true, ms: boolean = false): string {
        const millisecondValue = this.value * time

        let hour = this.format(millisecondValue / TimeUnit.HOUR_VALUE)
        hour = this.ifEmpty(hour, h)

        let minute = this.format(( millisecondValue % TimeUnit.HOUR_VALUE ) / TimeUnit.MINUTE_VALUE)
        minute = this.ifEmpty(minute, m)

        let second = this.format(( millisecondValue % TimeUnit.MINUTE_VALUE ) / TimeUnit.SECOND_VALUE)
        second = this.ifEmpty(second, s)

        let millisecond = ms ? this.format(millisecondValue % TimeUnit.SECOND_VALUE)
            : StrUtil.EMPTY

        return [ hour, minute, second, millisecond ]
            .filter(s => StrUtil.isNotEmpty(s))
            .join(':')
    }

    private ifEmpty(timeStr: string, initiative: boolean) {
        const empty = '00'
        if (timeStr !== empty) {
            return timeStr
        }
        return initiative ? timeStr : StrUtil.EMPTY
    }

    private format(value: number): string {
        let s: string | number = Math.floor(value)
        if (s < 10) {
            s = '0' + s
        }
        return String(s)
    }

}
